Voy a inaugurar una nueva serie de entradas dedicadas a IPython hablando de cosas que no son tan evidentes o que la gente no suele usar pero que están ahí y son tremendamente útiles una vez que las conoces y las incluyes en tu flujo de trabajo.
Inicialmente se iba a llamar '305928 cosas sobre IPython que no sabías (o sí) y que nunca tuviste tiempo de preguntar o de leer en la documentación o de leer en el código o nunca te hicieron falta o te estabas tomando unas cañas o estabas haciendo otras cosas más seductoras e inconfesables o {0}' pero me pareció un título un poco largo. Finalmente lo he reducido a 157 y pico, más o menos, para vuestra salud mental y la mia. El nombre está inspirado de forma muy libre en el nombre de la charla dada por Victor Terrón en la PyConES 2013 (¿que no la has visto? venga, a hacer los deberes, rápido. Y después vuelve por aquí).
Primero de todo, [mode taliban ON] se escribe IPython, las dos primeras en mayúscula y no iPython o ipython o aipaizon o IPhyton (sic) [mode taliban OFF]. Por tanto, recordadlo si no queréis que la vena de la frente de Mathias Bussonier se hinche.
Todos sabéis lo que es IPhyton (:-)), si no es así le puedes echar un vistazo a la documentación que está en la página oficial o visitar este vídeo que preparó JuanLu sobre el notebook. Si, aun así, sois tan vagos como yo y no queréis ver nada de lo anterior os cuento, como brevísimo resumen, que IPython es una consola interactiva con super poderes y magia negra incluida.
Vamos a empezar con cosas muy sencillas como el uso de la ayuda que ofrece IPython. Normalmente, para obtener ayuda de un objeto se usa el comando help()
, en IPython se puede usar la ayuda usando símbolos de interrogación. Si se usa un solo símbolo de interrogación se obtiene información general del objeto mientras que si se usan dos símbolos de interrogación se puede acceder a la implementación misma del objeto (solo en el caso de que haya sido programado en Python). Por ejemplo, veamos la ayuda del objeto calendar
dentro de la biblioteca calendar
disponible en la librería estándar y programada en Python.
In [1]:
from calendar import calendar
In [2]:
?calendar
Nos daría la siguiente información (os saltará una ventana en la parte inferior del navegador):
Type: method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File: /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Docstring: Returns a year's calendar as a multi-line string.
Class Docstring:
method(function, instance)
Create a bound instance method object.
[Nota] El comando anterior es equivalente a hacer calendar?
y también es equivalente a usar %pinfo calendar
.
Si ahora usamos el doble signo de interrogación obtendremos información mucho más detallada como el fichero en el que se encuentra la función y el código usado para implementarla, entre otras cosas:
In [3]:
??calendar
La siguiente información saldrá en una ventana en la parte inferior del navegador
Type: method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File: /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Source:
def formatyear(self, theyear, w=2, l=1, c=6, m=3):
"""
Returns a year's calendar as a multi-line string.
"""
w = max(2, w)
l = max(1, l)
c = max(2, c)
colwidth = (w + 1) * 7 - 1
v = []
a = v.append
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
a('\n'*l)
header = self.formatweekheader(w)
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
# months in this row
months = range(m*i+1, min(m*(i+1)+1, 13))
a('\n'*l)
names = (self.formatmonthname(theyear, k, colwidth, False)
for k in months)
a(formatstring(names, colwidth, c).rstrip())
a('\n'*l)
headers = (header for k in months)
a(formatstring(headers, colwidth, c).rstrip())
a('\n'*l)
# max number of weeks for this row
height = max(len(cal) for cal in row)
for j in range(height):
weeks = []
for cal in row:
if j >= len(cal):
weeks.append('')
else:
weeks.append(self.formatweek(cal[j], w))
a(formatstring(weeks, colwidth, c).rstrip())
a('\n' * l)
return ''.join(v)
Class Docstring:
method(function, instance)
Create a bound instance method object.
[Nota] El comando anterior es equivalente a hacer calendar??
y también es equivalente a usar %pinfo2 calendar
.
Si ahora usamos la ayuda usual disponible, función help()
de la siguiente forma help(calendar)
, veremos que la información es bastante más escueta que la obtenida mediante IPython.
In [4]:
help(calendar)
Con el signo de interrogación también podemos usar wildcards para obtener todos los objetos que cumplen el criterio. Por ejemplo, nos acordamos que el otro día usamos una función que empezaba por ca pero no nos acordamos del nombre completo. Podemos buscar todos los objetos que se encuentran en el namespace de la siguiente forma:
In [5]:
ca*?
Y IPython nos dará lo siguiente:
calendar
callable
Esto no es excesivamente útil ya que IPython ofrece autocompletado con la tecla de tabulación y llegaríamos al mismo resultado de forma sencilla. Pero, ¿y si nos acordamos que el objeto usado terminaba por ar en lugar de empezar por ca? En este caso, el autocompletado no nos resultaría de mucha ayuda. Pero podríamos usar lo siguiente:
In [6]:
*ar?
Lo anterior nos daría lo siguiente:
calendar
Pero no, no era lo que buscaba. En realidad estaba buscando un objeto que contenía ar
. Lo podemos buscar de la siguiente forma.
In [7]:
*ar*?
Lo anterior nos daría lo siguiente:
BytesWarning
DeprecationWarning
FutureWarning
ImportWarning
KeyboardInterrupt
PendingDeprecationWarning
ResourceWarning
RuntimeWarning
SyntaxWarning
UnicodeWarning
UserWarning
Warning
bytearray
calendar
vars
También le podéis echar un ojo a %pdef
, %pdoc
, %psource
o %pfile
para obtener información de diverso tipo sobre el objeto
de turno.
La ayuda del notebook sale en una zona inferior de la ventana del navegador. En general me resulta más incómoda que si se imprimiese como un ouput estándar dentro del navegador mismo. Con la siguiente receta, un poco modificada por mí, podemos hacer que determinada información salga inline:
In [10]:
## Importamos distintas funciones del módulo oinspect de IPython.
## Muchas de estas funciones son un wrapper sobre las funciones del
## módulo inspect de la stdlib.
from IPython.core.oinspect import (getsource, getdoc,
find_file, find_source_lines)
## Importamos lo siguiente para mostrar la información en pantalla
from IPython.display import display, HTML
## Importamos lo siguiente para convertir nuestra ayuda a magig functions
from IPython.core.magic import Magics, magics_class, line_magic
## Los siguientes imports serán usados para resaltar la sintáxis del código
## fuente.
## Es necesario tener instalada la librería pygments.
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
## Llamamos a la InteractiveShell y obtenemos el namespace
## del usuario (user_ns) que es un diccionario con los
## objetos disponibles
ip = get_ipython()
my_ns = ip.user_ns
@magics_class
class KikoMagic(Magics):
def __init__(self, shell):
super(KikoMagic, self).__init__(shell)
@line_magic
def kdoc(self, obj):
"""
Retrieve the info of an object and display this info in an output.
"""
if obj in my_ns.keys():
print("Doc info for {}:\n".format(obj))
print(getdoc(my_ns[obj]))
else:
print("There is no info for {}".format(obj))
@line_magic
def kfile(self, obj):
"""
Retrieve the file where the object is implemented.
"""
if obj in my_ns.keys():
print("{} implemented in file:\n".format(obj))
print(find_file(my_ns[obj]))
else:
print("We can't not find the file for {}".format(obj))
@line_magic
def ksourceline(self, obj):
"""
Retrieve the first line in the source file where
the object is implemented.
"""
if obj in my_ns.keys():
print("The implementation of {}".format(obj))
print("starts at line {}".format(find_source_lines(my_ns[obj])))
print("in file {}".format(find_file(my_ns[obj])))
else:
print("We can't not find the file for {}".format(obj))
@line_magic
def ksource(self, obj):
"""
Retrieve the info and the source of an object and
display the info in an output.
"""
formatter = HtmlFormatter(linenos=False, cssclass="source", nobackground=True)
template = """<style>{}</style>{}"""
src = getsource(my_ns[obj])
html = highlight(src, PythonLexer(), formatter)
css = formatter.get_style_defs()
display(HTML(template.format(css,html)))
@line_magic
def khelp(self, obj):
self.kdoc(obj)
print("")
self.ksourceline(obj)
print("")
self.ksource(obj)
## Registramos las nuevas funciones mágicas para que estén
## disponibles en el nb.
ip.register_magics(KikoMagic)
Dos de las funciones mágicas solo funcionarán en el notebook y no en la consola de IPython ya que estamos haciendo uso de display(HTML(...))
y la qtconsole, por ejemplo, no es capaz de mostrar el código html de forma correcta. Ahora usamos las nuevas funciones mágicas que acabamos de crear:
In [11]:
%kdoc calendar
In [12]:
%kfile calendar
In [13]:
%ksourceline calendar
In [14]:
%ksource calendar
In [15]:
%khelp calendar
¡¡Eyyy, qué chulo!! Acabamos de personalizar un poco el notebook de IPython de forma muy sencilla.
Si solo queremos hacer una función mágica lo podemos hacer de forma un poco más sencilla que la vista anteriormente. En el siguiente código se muestra cómo (receta hecha por Brian Granger y que he actualizado a las últimas versiones de IPython para hacerla funcionar):
In [16]:
from IPython.core.oinspect import getdoc
ip = get_ipython()
my_ns = ip.user_ns
## Definimos la función que hace lo que queremos. La siguiente
## función hace lo mismo que la función mágica kdoc que hemos
## implementado anteriormente
def new_magic(obj):
if obj in my_ns.keys():
print(getdoc(my_ns[obj]))
else:
print("No info found for {}".format(obj))
## En la siguiente línea definimos la anterior función como
## una 'line magic' que se llamará 'my_new_magic'
ip.register_magic_function(new_magic, 'line', "my_new_magic")
Ahora la vamos a hacer funcionar:
In [17]:
%my_new_magic calendar
Hemos visto como:
usar la ayuda de IPython, más avanzada que el uso de la función built-in help()
,
uso de comodines (wildcards),
las funciones mágicas útiles para obtener ayuda de Python,
como crear nuestras propias funciones mágicas de ayuda (o de lo que queráis).
Si se os ocurre algo para completar esta información podéis hacer un pull request a nuestro repo de notebooks y actualizaremos la información.